﻿namespace MvvmTools.Tests
{
    using System;
    using System.Diagnostics;
    using NUnit.Framework;
    using Shouldly;

    internal class EqualityComparerFixture
    {
        private const int Count = 1000;
        private Sample _sample;
        private Sample _impl2;
        private Sample _impl3;
        private Sample _impl4;
        private Sample _impl5;

        [SetUp]
        public void SetUp()
        {
            _sample = new Sample(1, "a", null);
            _impl2 = new Sample(1, "a", null);
            _impl3 = new Sample(2, "a", null);
            _impl4 = new Sample(1, "b", null);
            _impl5 = new Sample(1, "a", "b");
        }

        [Test]
        public void ShouldProvideGetHashCode()
        {
            // Given
            
            // When
            var code1 = _sample.GetHashCode();
            var code2 = _impl2.GetHashCode();
            var code3 = _impl3.GetHashCode();
            var code4 = _impl4.GetHashCode();
            var code5 = _impl5.GetHashCode();

            // Then
            code1.ShouldBe(code2);
            code1.ShouldNotBe(code3);
            code1.ShouldNotBe(code4);
            code1.ShouldNotBe(code5);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void ShouldThrowArgumentNullExceptionWhenInvokeDependsWithNullAccessor()
        {
            // Given
            
            // When

            // ReSharper disable once AssignNullToNotNullAttribute
            EqualityComparer<Sample>.Depends<string>(null);

            // Then
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void ShouldThrowArgumentNullExceptionWhenInvokeDependsNullableWithNullAccessor()
        {
            // Given

            // When
            
            // ReSharper disable once AssignNullToNotNullAttribute
            EqualityComparer<Sample>.DependsNullable<string>(null);

            // Then
        }

        [Test]
        public void ShouldProvideGenericEquOp()
        {
            // Given
            
            // When
            var res1 = _sample.Equals(_sample);
            var res2 = _sample.Equals(_impl2);
            var res3 = _sample.Equals(_impl3);
            var res4 = _sample.Equals(_impl4);
            var res5 = _sample.Equals(_impl5);

            // Then
            res1.ShouldBe(true);
            res2.ShouldBe(true);
            res3.ShouldBe(false);
            res4.ShouldBe(false);
            res5.ShouldBe(false);
        }

        [Test]
        public void ShouldProvideEquOp()
        {
            // Given
            // When
            // ReSharper disable once EqualExpressionComparison
            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
            var res1 = Equals(_sample, _sample);
            var res2 = Equals(_sample, _impl2);
            var res3 = Equals(_sample, _impl3);
            var res4 = Equals(_sample, _impl4);
            var res5 = Equals(_sample, _impl5);

            // Then
            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
            res1.ShouldBe(true);
            res2.ShouldBe(true);
            res3.ShouldBe(false);
            res4.ShouldBe(false);
            res5.ShouldBe(false);
        }

        [Test]
        public void ShouldEqualityPerfomanceTestOrigin()
        {
            // Given
            var impl = new Orinig(1, "a", null);
            var res = 0;

            // When
            var stopwatch = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                res = impl.GetHashCode();
            }
            
            stopwatch.Stop();

            // Then
            Trace.WriteLine("Origin: " + stopwatch.ElapsedMilliseconds);
            (res != 0).ShouldBe(true);
        }

        [Test]
        public void ShouldEqualityPerfomanceTestSample()
        {
            // Given
            var impl = new Sample(1, "a", null);
            int res = 0;

            // When
            var stopwatch = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                res = impl.GetHashCode();
            }
            
            stopwatch.Stop();

            // Then
            Trace.WriteLine("Sample: " + stopwatch.ElapsedMilliseconds);
            (res != 0).ShouldBe(true);
        }

        private class Sample : IEquatable<Sample>
        {
            private static readonly IEqualityComparerEx<Sample> EqualityComparer = EqualityComparer<Sample>
                .Depends(i => i._val)
                .Depends(i => i._str)
                .DependsNullable(i => i._nullStr)
                .Depends(i => i._val1)
                .Depends(i => i._val2)
                .Depends(i => i._val3)
                .Depends(i => i._val4)
                .Depends(i => i._val5)
                .Depends(i => i._val6)
                .Depends(i => i._val7)
                .Depends(i => i._val8)
                .Depends(i => i._val9)
                .Depends(i => i._val10)
                .Depends(i => i._val11)
                .Depends(i => i._val12)
                .Depends(i => i._val13)
                .Depends(i => i._val14)
                .Depends(i => i._val15)
                .Depends(i => i._val16)
                .Depends(i => i._val17)
                .Depends(i => i._val18)
                .Depends(i => i._val19)
                .Depends(i => i._val20)
                .Create();

            private readonly int _val;
            private readonly string _str;
            private readonly string _nullStr;

            // ReSharper disable ConvertToConstant.Local
            private readonly int _val1 = 1, _val2 = 2, _val3 = 3, _val4 = 4, _val5 = 5, _val6 = 6, _val7 = 7, _val8 = 8, _val9 = 9, _val10 = 10;
            private readonly int _val11 = 1, _val12 = 2, _val13 = 3, _val14 = 4, _val15 = 5, _val16 = 6, _val17 = 7, _val18 = 8, _val19 = 9, _val20 = 10;

            // ReSharper restore ConvertToConstant.Local
            public Sample(int val, string str, string nullStr)
            {
                _val = val;
                _str = str;
                _nullStr = nullStr;
            }

            public override int GetHashCode()
            {
                return EqualityComparer.GetHashCode(this);
            }

            public override bool Equals(object obj)
            {
                return EqualityComparer.Equals(this, obj);
            }

            public bool Equals(Sample other)
            {
                return EqualityComparer.Equals(this, other);
            }
        }

        private class Orinig
        {
            private readonly int _val;
            private readonly string _str;
            private readonly string _nullStr;

            // ReSharper disable ConvertToConstant.Local
            private readonly int _val1 = 1, _val2 = 2, _val3 = 3, _val4 = 4, _val5 = 5, _val6 = 6, _val7 = 7, _val8 = 8, _val9 = 9, _val10 = 10;
            private readonly int _val11 = 1, _val12 = 2, _val13 = 3, _val14 = 4, _val15 = 5, _val16 = 6, _val17 = 7, _val18 = 8, _val19 = 9, _val20 = 10;

            // ReSharper restore ConvertToConstant.Local
            public Orinig(int val, string str, string nullStr)
            {
                _val = val;
                _str = str;
                _nullStr = nullStr;
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }

                if (ReferenceEquals(this, obj))
                {
                    return true;
                }

                if (obj.GetType() != GetType())
                {
                    return false;
                }
                
                return Equals((Orinig)obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    var hashCode = _val;
                    hashCode = (hashCode * 397) ^ _str.GetHashCode();
                    hashCode = (hashCode * 397) ^ (_nullStr != null ? _nullStr.GetHashCode() : 0);
                    hashCode = (hashCode * 397) ^ _val1;
                    hashCode = (hashCode * 397) ^ _val2;
                    hashCode = (hashCode * 397) ^ _val3;
                    hashCode = (hashCode * 397) ^ _val4;
                    hashCode = (hashCode * 397) ^ _val5;
                    hashCode = (hashCode * 397) ^ _val6;
                    hashCode = (hashCode * 397) ^ _val7;
                    hashCode = (hashCode * 397) ^ _val8;
                    hashCode = (hashCode * 397) ^ _val9;
                    hashCode = (hashCode * 397) ^ _val10;
                    hashCode = (hashCode * 397) ^ _val11;
                    hashCode = (hashCode * 397) ^ _val12;
                    hashCode = (hashCode * 397) ^ _val13;
                    hashCode = (hashCode * 397) ^ _val14;
                    hashCode = (hashCode * 397) ^ _val15;
                    hashCode = (hashCode * 397) ^ _val16;
                    hashCode = (hashCode * 397) ^ _val17;
                    hashCode = (hashCode * 397) ^ _val18;
                    hashCode = (hashCode * 397) ^ _val19;
                    hashCode = (hashCode * 397) ^ _val20;
                    return hashCode;
                }
            }

            private bool Equals(Orinig other)
            {
                return _val == other._val && string.Equals(_str, other._str) && string.Equals(_nullStr, other._nullStr) && _val1 == other._val1 && _val2 == other._val2 && _val3 == other._val3 && _val4 == other._val4 && _val5 == other._val5 && _val6 == other._val6 && _val7 == other._val7 && _val8 == other._val8 && _val9 == other._val9 && _val10 == other._val10 && _val11 == other._val11 && _val12 == other._val12 && _val13 == other._val13 && _val14 == other._val14 && _val15 == other._val15 && _val16 == other._val16 && _val17 == other._val17 && _val18 == other._val18 && _val19 == other._val19 && _val20 == other._val20;
            }
        }
    }
}
